{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7eedde7eb8a918f2",
   "metadata": {},
   "source": [
    "# 2.6.用插件扩展答疑机器人的能力边界\n",
    "## 🚄 前言\n",
    "在前面课程中，你已经掌握了通过优化提示词和检索流程提高答疑机器人效果的方法。但目前的答疑机器人依然有一定的局限性，本章节将带你发掘这些不足并引入智能体（Agent）应用以解决这些问题，它以大模型为基础，同时可以拓展大模型的能力，就像给大脑配备了四肢。\n",
    "\n",
    "## 🍁 课程目标\n",
    "学完本节课程后，你将能够：\n",
    "\n",
    "* 掌握Agent系统的核心理念\n",
    "* 熟悉Multi-Agent系统的设计与实现\n",
    "* 掌握核心工具与框架以应对复杂任务\n",
    "\n",
    "## 1.机器人的局限性与解决方案\n",
    "一些同事希望答疑机器人能具备这样一种功能：只需说出“帮我请明天的假”，机器人便能自动提交请假申请单。然而，传统的答疑机器人有其固有局限性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e15a26ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入所需的依赖包\n",
    "from config.load_key import load_key\n",
    "import os\n",
    "# 加载API密钥\n",
    "load_key()\n",
    "# 生产环境中请勿将 API Key 输出到日志中，避免泄露\n",
    "print(f'''你配置的 API Key 是：{os.environ[\"DASHSCOPE_API_KEY\"][:5]+\"*\"*5}''')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22bec1ff4338c447",
   "metadata": {},
   "outputs": [],
   "source": [
    "from fontTools.ttLib.tables.ttProgram import instructions\n",
    "\n",
    "from chatbot import rag\n",
    "# 上一章节已经建立了索引，因此这里可以直接加载索引。如果需要重建索引，可以增加一行代码：rag.indexing()\n",
    "index = rag.load_index()\n",
    "query_engine = rag.create_query_engine(index=index)\n",
    "\n",
    "rag.ask('张三的HR明天想请个假', query_engine=query_engine)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cac5549d5ce2a23f",
   "metadata": {},
   "source": [
    "从上面的示例中你会发现当前大模型只是文本输入输出的问答系统无法和外界交互。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i3/O1CN01rdstgY1uiZWt8gqSL_!!6000000006071-0-tps-1970-356.jpg\" alt=\"image\" width=\"600\" hight=\"300\">\n",
    "\n",
    "\n",
    "为了解决这个问题，你可以为答疑机器人引入一种新能力：动态解析用户需求并采取相应行动。例如，为了让问答机器人能够帮助用户请假，你需要让大模型解析用户的需求，并调用相应的API（如请假API）。这正是智能体应用的核心思想——通过任务分解和自动化执行，智能体能够高效地响应并完成复杂的操作。\n",
    "\n",
    "<img src=\"https://gw.alicdn.com/imgextra/i4/O1CN01Xud4YB1D1UniMmUaN_!!6000000000156-0-tps-952-410.jpg\" alt=\"image\" width=\"600\" hight=\"700\">\n",
    "\n",
    "## 2.如何构建 Agent\n",
    "\n",
    "通常来说构建一个智能体分为以下步骤，你将如下图所示一步步完成一个智能体的构建。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i3/O1CN01wyaLNO1VE0hqPKAIm_!!6000000002620-0-tps-1083-91.jpg\" alt=\"image\" width=\"600\">\n",
    "\n",
    "### 2.1明确目标\n",
    "“不谋全局者，不足谋一域。” —— 在任何复杂任务中，明确目标都是迈向成功的第一步。正如绘制一张地图需要先确定目的地，在构建答疑机器人时，你也需要先清晰地定义任务的核心目标。\n",
    "\n",
    "你想要答疑机器人能够从公司的私有数据库中查询员工信息，并能够帮助用户完成请假申请的同时在数据库中进行记录与更新。\n",
    "\n",
    "所以你的第一个目标是：将用户所有对公司内人员信息类型的提问转化为数据库查询的工具函数。 具体而言，这包括：\n",
    "\n",
    "- 将用户输入的自然语言问题转化为对应的 SQL 查询语句（即 NL2SQL，自然语言转 SQL）。\n",
    "- 使用生成的 SQL 查询语句访问数据库，获取对应的查询结果。\n",
    "- 将查询结果作为工具函数的输出，最终返回给用户。\n",
    "\n",
    "\n",
    "\n",
    "### 2.2定义工具函数\n",
    "接下来，在配置好环境变量后你就可以开始搭建一个Agent智能体了。\n",
    "\n",
    "当然，从零来构建一个Agent需要处理复杂的底层实现，这往往需要大量的时间和精力，因此你可以使用 Assistant API 来帮助你更高效地构建 Agent。\n",
    "\n",
    "Assistant API 是一种简化智能体应用创建过程的接口。它提供了丰富的功能，包括支持多种基础模型、灵活的工具调用、对话管理和高度可扩展性。\n",
    "\n",
    "通过 Assistant API，你可以专注于智能体的核心功能，而不需要处理繁琐的底层实现。\n",
    "\n",
    "首先你需要定义一些工具函数，假设你的答疑机器人需要有从数据库查询员工信息的功能。\n",
    "\n",
    "为了帮助你更多地关注到Agent的内容，你需要模拟一个查询步骤，而不实际去数据库中查询。\n",
    "\n",
    "> 假设员工表名为employee，字段包括department（部门）、name（姓名）、HR。\n",
    "\n",
    "> 如果你希望继续提升大模型NL2SQL的性能，请前往微调教程学习。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "47c805d33e735992",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:47:34.174726Z",
     "start_time": "2024-11-27T13:47:32.834912Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SQL语句为：SELECT COUNT(*) FROM employees WHERE department = '教育部门';\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'教育部门共有66名员工。'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 导入依赖\n",
    "from llama_index.llms.dashscope import DashScope\n",
    "from llama_index.core.base.llms.types import MessageRole, ChatMessage\n",
    "\n",
    "# 定义一个员工查询函数\n",
    "def query_employee_info(query):\n",
    "    '''\n",
    "    输入用户提问，输出员工信息查询结果\n",
    "    '''\n",
    "    # 1. 首先根据用户提问，使用NL2SQL生成SQL语句\n",
    "    llm = DashScope(model_name=\"qwen-plus\")\n",
    "    messages = [\n",
    "        ChatMessage(role=MessageRole.SYSTEM, content='''你有一个表叫employees，记录公司的员工信息，这个表有department（部门）、name（姓名）、HR三个字段。\n",
    "    你需要根据用户输入生成sql语句进行查询，只生成sql语句，不需要生成sql语句之外的内容，也不要把```sql```这个标签加上。'''),\n",
    "        ChatMessage(role=MessageRole.USER, content=query)\n",
    "    ]\n",
    "    SQL_output = llm.chat(messages).message.content\n",
    "    # 打印出SQL语句\n",
    "    print(f'SQL语句为：{SQL_output}')\n",
    "    # 2. 根据SQL语句去查询数据库（此处为模拟查询），并返回结果\n",
    "    if SQL_output == \"SELECT COUNT(*) FROM employees WHERE department = '教育部门';\":\n",
    "        return \"教育部门共有66名员工。\"\n",
    "    if SQL_output == \"SELECT HR FROM employees WHERE name = '张三';\":\n",
    "        return \"张三的HR是李四。\"\n",
    "    if SQL_output == \"SELECT department FROM employees WHERE name = '王五';\":\n",
    "        return \"王五的部门是后勤部。\"\n",
    "    else:\n",
    "        return \"抱歉，我暂时无法回答您的问题。\"\n",
    "\n",
    "# 测试一下这个函数\n",
    "query_employee_info(\"教育部门有几个人\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a37799847984b20",
   "metadata": {},
   "source": [
    "### 2.3将工具函数与大模型集成进Agent中\n",
    "你已经定义好了工具函数，接下来就要将它们与大模型通过Assistant API集成到Agent中。\n",
    "\n",
    "通过Assistants.create方法，你可以创建一个新的Agent，并通过model（模型名称）、name（Agent命名）、description（Agent的描述信息）、instructions（Agent非常重要的参数，用于提示Agent所具有的工具函数能力，同时也可以规范输出格式）、tools（工具函数通过该参数传入）参数来定义Agent。\n",
    "\n",
    "> 其中，tools参数中的function.name用于指定工具函数，但需要为字符串格式，因此可以通过一个map方法映射到工具函数上。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96157c07000688b",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:47:40.231962Z",
     "start_time": "2024-11-27T13:47:40.012926Z"
    }
   },
   "outputs": [],
   "source": [
    "# 引入依赖\n",
    "from dashscope import Assistants, Messages, Runs, Threads\n",
    "import json\n",
    "\n",
    "# 定义公司小蜜\n",
    "ChatAssistant = Assistants.create(\n",
    "    # 在此指定模型名称\n",
    "    model=\"qwen-plus\",\n",
    "    # 在此指定Agent名称\n",
    "    name='公司小蜜',\n",
    "    # 在此指定Agent的描述信息\n",
    "    description='一个智能助手，能够查询员工信息，帮助员工发送请假申请，或者查询公司规章制度。',\n",
    "    # 用于提示大模型所具有的工具函数能力，也可以规范输出格式\n",
    "    instructions='''你是公司小蜜，你的功能有以下三个：\n",
    "    1. 查询员工信息。例如：查询员工张三的HR是谁；\n",
    "    2. 发送请假申请。例如：当员工提出要请假时，你可以在系统里帮他完成请假申请的发送；\n",
    "    3. 查询公司规章制度。例如：我们公司项目管理的工具是什么？\n",
    "    请准确判断需要调用哪个工具，并礼貌回答用户的提问。\n",
    "    ''',\n",
    "    # 将工具函数传入\n",
    "    tools=[\n",
    "        {\n",
    "            # 定义工具函数类型，一般设置为function即可\n",
    "            'type': 'function',\n",
    "            'function': {\n",
    "                # 定义工具函数名称，通过map方法映射到query_employee_info函数\n",
    "                'name': '查询员工信息',\n",
    "                # 定义工具函数的描述信息，Agent主要根据description来判断是否需要调用该工具函数\n",
    "                'description': '当需要查询员工信息时非常有用，比如查询员工张三的HR是谁，查询教育部门总人数等。',\n",
    "                # 定义工具函数的参数\n",
    "                'parameters': {\n",
    "                    'type': 'object',\n",
    "                    'properties': {\n",
    "                        # 将用户的提问作为输入参数\n",
    "                        'query': {\n",
    "                            'type': 'str',\n",
    "                            # 对输入参数的描述\n",
    "                            'description': '用户的提问。'\n",
    "                        },\n",
    "                    },\n",
    "                    # 在此声明该工具函数需要哪些必填参数\n",
    "                    'required': ['query']},\n",
    "            }\n",
    "        }\n",
    "    ]\n",
    ")\n",
    "print(f'{ChatAssistant.name}创建完成')\n",
    "# 建立Agent Function name与工具函数的映射关系\n",
    "function_mapper = {\n",
    "    \"查询员工信息\": query_employee_info\n",
    "}\n",
    "print('工具函数与function.name映射关系建立完成')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b73f3ccc63d1fa",
   "metadata": {},
   "source": [
    "同时，你还可以封装一个辅助函数`get_agent_response`。\n",
    "\n",
    "这段代码的功能是：当用户向智能体发出请求时，智能体通过 get_agent_response() 发送请求并获取响应。如果任务需要调用外部工具（如数据库查询），则智能体会根据工具函数的映射执行相应的操作，并将结果返回给用户。这使得智能体能够处理更复杂的任务，而不仅仅是简单的问答。\n",
    "\n",
    "通过Assistant API获得Agent回复的过程需要涉及到如thread、message、run等概念，如果你对这些概念与细节感兴趣，请参考[阿里云Assistant API官方文档](https://help.aliyun.com/zh/model-studio/developer-reference/assistantapi/)。\n",
    "\n",
    "> 如果你希望给Agent配备更多的能力，可以添加工具函数，并在function_mapper与tools中建立映射关系。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a43db31e692ebdb",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:47:47.719482Z",
     "start_time": "2024-11-27T13:47:47.713359Z"
    }
   },
   "outputs": [],
   "source": [
    "# 输入message信息，输出为指定Agent的回复\n",
    "def get_agent_response(assistant, message=''):\n",
    "    # 创建一个新的会话线程\n",
    "    thread = Threads.create()\n",
    "    \n",
    "    # 创建一条消息并发送到该会话线程\n",
    "    message = Messages.create(thread.id, content=message)\n",
    "    \n",
    "    # 创建一个运行实例（运行请求），将会话线程与Assistant（智能体）关联起来\n",
    "    run = Runs.create(thread.id, assistant_id=assistant.id)\n",
    "    \n",
    "    # 等待运行完成，检查任务是否完成\n",
    "    run_status = Runs.wait(run.id, thread_id=thread.id)\n",
    "    \n",
    "    # 如果任务运行失败，则输出错误信息\n",
    "    if run_status.status == 'failed':\n",
    "        print('run failed:')\n",
    "        \n",
    "    # 如果需要工具来辅助模型进行操作（如查询数据库、发送请求等）\n",
    "    if run_status.required_action:\n",
    "        # 获取需要调用的工具函数的详细信息\n",
    "        f = run_status.required_action.submit_tool_outputs.tool_calls[0].function\n",
    "        \n",
    "        # 获取工具函数的名称（function name）\n",
    "        func_name = f['name']\n",
    "        \n",
    "        # 获取调用工具函数时需要的输入参数\n",
    "        param = json.loads(f['arguments'])\n",
    "        \n",
    "        # 打印出工具的名称和参数信息\n",
    "        print(\"function is\", f)\n",
    "        \n",
    "        # 根据工具函数的名称，通过一个映射（function_mapper）找到对应的实际工具函数\n",
    "        # 这里使用了一个字典映射（function_mapper），它将工具函数名称与具体的函数对应\n",
    "        if func_name in function_mapper:\n",
    "            # 使用映射找到实际工具函数并传递参数，获取结果\n",
    "            output = function_mapper[func_name](**param)\n",
    "        else:\n",
    "            # 如果找不到对应的函数，输出为空\n",
    "            output = \"\"\n",
    "        \n",
    "        # 将工具函数的输出（结果）准备提交\n",
    "        tool_outputs = [{\n",
    "            'output': output\n",
    "        }]\n",
    "        \n",
    "        # 提交工具的输出结果回给运行实例\n",
    "        run = Runs.submit_tool_outputs(run.id, thread_id=thread.id, tool_outputs=tool_outputs)\n",
    "        \n",
    "        # 等待运行完成\n",
    "        run_status = Runs.wait(run.id, thread_id=thread.id)\n",
    "    \n",
    "    # 获取最终的运行结果\n",
    "    run_status = Runs.get(run.id, thread_id=thread.id)\n",
    "    \n",
    "    # 获取消息记录列表\n",
    "    msgs = Messages.list(thread.id)\n",
    "    \n",
    "    # 返回Agent的回复内容\n",
    "    return msgs['data'][0]['content'][0]['text']['value']\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dc81c8d7be82469",
   "metadata": {},
   "source": [
    "### 2.4尝试对话\n",
    "你已经完成了一个简单的单Agent系统构建，在正式投入使用之前测试是必不可少的一环，你可以尝试与答疑机器人进行对话："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "951ce7f63060d5cd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:09.733102Z",
     "start_time": "2024-11-27T13:47:52.311162Z"
    }
   },
   "outputs": [],
   "source": [
    "query_stk = [\n",
    "    \"谁是张三的HR？\",\n",
    "    \"教育部门一共有多少员工？\",\n",
    "    \"王五在哪个部门？\",\n",
    "]\n",
    "for query in query_stk:\n",
    "    print(\"提问是：\")\n",
    "    print(query)\n",
    "    print(\"思考过程与最终输出是：\")\n",
    "    print(get_agent_response(ChatAssistant,query))\n",
    "    print(\"\\n\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d420da16ad5845af",
   "metadata": {},
   "source": [
    "从测试结果可以看出，拓展了功能之后的答疑机器人达到了你预期的效果。\n",
    "\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i4/O1CN01q6NyMr237JtdJFO8Q_!!6000000007208-2-tps-2082-974.png\" alt=\"image\" width=\"600\" height=\"300\">\n",
    "\n",
    "在实际应用中，智能体不仅可以与外界交互，还能通过不同的模块化设计来增强其处理复杂任务的能力。智能体的工作原理可以从以下几个核心模块进行理解：\n",
    "\n",
    "- 工具模块\n",
    "\n",
    "    工具模块负责定义和管理智能体能够使用的各种工具。包括工具的描述、参数以及功能特性。这一模块确保智能体能够理解并有效使用这些工具来完成任务。\n",
    "\n",
    "- 记忆模块\n",
    "\n",
    "    记忆模块可以分为长期记忆和短期记忆。\n",
    "  \n",
    "    长期记忆用于存储持久的信息和经验，帮助智能体进行模式学习、知识积累和个性化服务。\n",
    "    \n",
    "    短期记忆则用于临时存储当前任务相关的信息，以支持智能体在任务执行过程中实时调整决策。\n",
    "- 计划能力\n",
    "\n",
    "    计划能力模块负责任务的规划。通过智能体的决策能力，这部分帮助智能体分解复杂任务，制定具体的行动步骤和策略，确保任务顺利完成。\n",
    "\n",
    "- 行动能力\n",
    "\n",
    "    行动能力与工具模块紧密配合，确保智能体能够选择合适的工具，并通过容器执行相应的操作。行动能力是智能体实现任务的核心，确保其能够根据既定计划和决策，有效地实施各项任务。\n",
    "\n",
    "通过这些模块的协作，智能体能够处理复杂任务，提升任务执行的效率和精准度，突破传统方法的局限。\n",
    "\n",
    "\n",
    "\n",
    "如果你对这些概念感兴趣，请参考[阿里云大模型ACA课程](https://edu.aliyun.com/course/3126500/lesson/342570389?spm=a2cwt.28196072.ACA13.12.660e6e7b6G8Ye7)与本章节拓展阅读部分。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7409e86faa0b127",
   "metadata": {},
   "source": [
    "## 3.多智能体Multi-Agent\n",
    "当完成员工信息的查询后，接下来你还需要对员工请假申请进行申请与记录，所以你需要再加一个新的工具函数以满足此需求。\n",
    "\n",
    "这个工具函数将员工输入的请假日期作为输入参数，并返回一个申请成功的字符串。为了帮助你更多地关注到Agent的内容，下方的示例模拟了请假申请步骤，而没有实际去公司系统中提交请假申请。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f4406c4b86cf3f0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:17.435802Z",
     "start_time": "2024-11-27T13:48:17.433057Z"
    }
   },
   "outputs": [],
   "source": [
    "def send_leave_application(date):\n",
    "    '''\n",
    "    输入请假时间，输出请假申请发送结果\n",
    "    '''\n",
    "    return f'已为你发送请假申请，请假日期是{date}。'\n",
    "\n",
    "# 测试一下这个函数\n",
    "print(send_leave_application(\"明后两天\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b08f286708563c3d",
   "metadata": {},
   "source": [
    "在确定新的工具函数正常工作后，你需要将这个新函数集成进你之前创建的agent中："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67e3c537d901679e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:20.921557Z",
     "start_time": "2024-11-27T13:48:20.917467Z"
    }
   },
   "outputs": [],
   "source": [
    "new_tool = {'type': 'function',\n",
    "            'function': {\n",
    "                'name': '发送请假申请',\n",
    "                'description': '当需要帮助员工发送请假申请时非常有用。',\n",
    "                'parameters': {\n",
    "                    'type': 'object',\n",
    "                    'properties': {\n",
    "                        # 需要请假的时间\n",
    "                        'date': {\n",
    "                            'type': 'str',\n",
    "                            'description': '员工想要请假的时间。'\n",
    "                        },\n",
    "                    },\n",
    "                    'required': ['date']},\n",
    "            }\n",
    "           }\n",
    "ChatAssistant.tools.append(new_tool)\n",
    "function_mapper[\"发送请假申请\"] = send_leave_application\n",
    "print('请假工具函数与function.name映射关系建立完成')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b55ff249b33e8eb",
   "metadata": {},
   "source": [
    "在确认集成成功后，你可以来测试一下模型的输出效果以确保一切功能正常运作："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6fb8ef361bd3c57",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:32.443239Z",
     "start_time": "2024-11-27T13:48:25.039757Z"
    }
   },
   "outputs": [],
   "source": [
    "get_agent_response(ChatAssistant,\"张三的HR是谁？给他请三天假\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf65d0197b4d84f7",
   "metadata": {},
   "source": [
    "通过上面的输出结果，你会发现在处理复杂任务时，特别是当机器人需要在一个请求中执行多个操作时，单个智能体可能无法有效完成所有子任务。\n",
    "\n",
    "例如，用户请求“张三的HR是谁？给他请三天假”，这就涉及到员工信息查询和请假申请两个操作。单个智能体通常只能处理一种任务，无法同时调动多个工具或API接口来完成所有子任务。\n",
    "\n",
    "为了克服这种多操作需求的局限性，你可以为答疑机器人引入一种新能力：将任务拆解成多个独立的模块进行处理，而多智能体系统正是为此而生。\n",
    "\n",
    "多智能体系统通过将任务拆解成多个子任务，并由不同的智能体分别处理这些任务，从而克服了单一智能体无法同时完成多个操作的局限性。每个智能体专注于一个特定任务，像一个团队中的成员，各司其职，最终协作完成整个任务。\n",
    "\n",
    "这种设计不仅能够提高任务处理的效率，还能增强灵活性，确保每个子任务得到专门的处理。\n",
    "\n",
    "Multi-Agent系统有多种设计思路，本教程将介绍一个由Planner Agent、若干个负责执行工具函数的Agent，以及一个Summary Agent组成的Multi-Agent系统。\n",
    "- Planner Agent规划智能体：\n",
    "    根据用户的输入内容，选择要将任务分发给哪个Agent或Agent组合完成任务。\n",
    "- 执行工具函数的Agent智能体：\n",
    "    根据Planner Agent分发的任务，执行属于自己的工具函数。\n",
    "- Summary Agent总结智能体：\n",
    "    根据用户的输入，以及执行工具函数的Agent的输出，生成总结并返回给用户。\n",
    "  \n",
    "<img src=\"https://img.alicdn.com/imgextra/i1/O1CN01kX9Asm1dNdxmW18nG_!!6000000003724-2-tps-2074-935.png\" alt=\"image\" width=\"800\" height=\"400\">\n",
    "\n",
    "回到之前的示例——“张三的HR是谁？给他请三天假”。在多智能体系统中，这个任务会被拆解成两个子任务：\n",
    "\n",
    "查询张三的HR信息：由一个Agent负责。\n",
    "\n",
    "发送请假申请：由另一个Agent负责。\n",
    "\n",
    "通过多智能体系统，Planner Agent首先分析用户请求并拆解成这两个子任务，然后将每个任务交给对应的执行Agent处理。最后，Summary Agent会将各个Agent的结果汇总，生成最终的响应。\n",
    "\n",
    "### 3.1 Planner Agent\n",
    "Planner Agent 是 Multi-Agent 系统的核心部分，它负责分析问题，并决定将任务分发到哪个Agent或Agent组合上。\n",
    "\n",
    "首先利用 Assistant API 创建 Planner Agent，此处你可以先不对instructions进行指定："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f3a59268d1e7196",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:43.435926Z",
     "start_time": "2024-11-27T13:48:43.254803Z"
    }
   },
   "outputs": [],
   "source": [
    "# 决策级别的agent，决定使用哪些agent，以及它们的运行顺序。\n",
    "planner_agent = Assistants.create(\n",
    "    model=\"qwen-plus\",\n",
    "    name='流程编排机器人',\n",
    "    description='你是团队的leader，你的手下有很多agent，你需要根据用户的输入，决定要以怎样的顺序去使用这些agent'\n",
    ")\n",
    "\n",
    "print(\"Planner Agent创建完成\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0f7bb11f9bce005",
   "metadata": {},
   "source": [
    "创建完成后你可以先来看看在未定义instructions时，Planner Agent 的输出是什么样的："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86841c00e3df44d3",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:54.215863Z",
     "start_time": "2024-11-27T13:48:45.896368Z"
    }
   },
   "outputs": [],
   "source": [
    "print(get_agent_response(planner_agent,'谁是张三的HR？教育部门一共有多少员工？'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6814c2899dcf365",
   "metadata": {},
   "source": [
    "从机器人的回答中你可以发现，其输出的回答包含许多额外的信息且没有告知所需要调用的Agent名称。\n",
    "\n",
    "接下来你需要通过 instructions 指定其可调用的 Agent 及输出格式。\n",
    "\n",
    "目前，你需要机器人能够帮助员工进行公司内部员工信息查询、请假申请以及其他日常对话。\n",
    "\n",
    "另外，由于 Agent 的返回值为字符串格式，当你后续需要通过返回内容来分别调用所对应的智能体时会产生不便，所以你需要要求 Planner Agent 输出列表形式的字符串。例如：`[\"employee_info_agent\", \"leave_agent\", \"company_info_agent\"]`，以便后续使用字符串解析工具将其转换为结构化数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1323242cd85b81e5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:48:59.623320Z",
     "start_time": "2024-11-27T13:48:59.415135Z"
    }
   },
   "outputs": [],
   "source": [
    "planner_agent=Assistants.update(planner_agent.id,instructions=\"\"\"你的团队中有以下agent。\n",
    "    employee_info_agent：可以查询公司的员工信息，如果提问中关于部门、HR等信息，则调用该agent；\n",
    "    leave_agent：可以帮助员工发送请假申请，如果用户提出请假，则调用该agent；\n",
    "    chat_agent：如果用户的问题无需以上agent，则调用该agent。\n",
    "\n",
    "    你需要根据用户的问题，判断要以什么顺序使用这些agent，一个agent可以被多次调用。你的返回形式是一个列表，不能返回其它信息。比如：[\"employee_info_agent\", \"leave_agent\"]或者[\"chat_agent\"]，列表中的元素只能为上述的agent。\"\"\")\n",
    "\n",
    "print(\"Planner Agent 的 instructions 已更新\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fb189bddd310284",
   "metadata": {},
   "source": [
    "接下来尝试几个测试问题，看下Planner Agent能否分发到正确的Agent。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3911e7283950f57",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:49:08.690594Z",
     "start_time": "2024-11-27T13:49:01.647212Z"
    }
   },
   "outputs": [],
   "source": [
    "query_stk = [\n",
    "    \"谁是张三的HR？教育部门一共有多少员工？\",\n",
    "    \"王五在哪个部门？帮我提交下周三请假的申请\",\n",
    "    \"你好\"\n",
    "]\n",
    "for query in query_stk:\n",
    "    print(\"提问是：\")\n",
    "    print(query)\n",
    "    print(get_agent_response(planner_agent,query))\n",
    "    print(\"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1560c7c819db8b16",
   "metadata": {},
   "source": [
    "对于这三个测试问题，Planner Agent都做出了正确的选择。\n",
    "\n",
    "你可以观察到当Planner Agent返回任务规划结果后，其输出是一个描述任务执行顺序的列表形式字符串，例如：[\"employee_info_agent\", \"leave_agent\"]。为了便于后续处理和执行，你需要将其转换为 Python 原生的列表结构（list）并保留相对应的调用顺序。在这里，你可以使用了 Python的ast.literal_eval方法，它可以安全地将字符串表达式解析为相应的 Python 数据类型，例如列表、字典等。\n",
    "\n",
    "通过这种方式，你可以将任务规划结果转化为易于操作的列表对象，并逐步解析出每个任务的执行步骤，以简化后续的多智能体协作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8903cb65aa1dca7b",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:49:18.448629Z",
     "start_time": "2024-11-27T13:49:16.021853Z"
    }
   },
   "outputs": [],
   "source": [
    "import ast\n",
    "\n",
    "# 使用Planner Agent获取任务规划\n",
    "planner_response = get_agent_response(planner_agent, \"王五在哪个部门？帮我提交下周三请假的申请\")\n",
    "\n",
    "# 将Planner Agent的字符串形式回复解析为列表\n",
    "# Planner Agent返回的是一个描述调用顺序的列表形式字符串，例如：[\"employee_info_agent\", \"leave_agent\"]\n",
    "order_stk = ast.literal_eval(planner_response)\n",
    "\n",
    "# 打印出Planner Agent的规划结果\n",
    "print(\"Planner Agent的任务规划结果：\")\n",
    "for i, agent in enumerate(order_stk, start=1):\n",
    "    print(f'第{i}步调用：{agent}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a3548968d442d87",
   "metadata": {},
   "source": [
    "### 3.2执行工具函数的Agent\n",
    "上一章节你已经完成了Planner Agent的规划工作。它如同蚁巢中的蚁后，能够统筹规划任务并下达命令。然而，单靠蚁后是不足以让整个蚁巢运转起来的——需要无数的工蚁去执行具体任务，比如搜集食物或修筑巢穴。同样的道理，在你的多智能体系统中，仅有 Planner Agent 还不足以完成任务，必须为其配备执行具体任务的 工具函数Agent，才能真正实现整个系统的高效协作。\n",
    "\n",
    "以下，你将需要基于上节中的规划结果，为两个不同任务创建独立的 执行工具函数Agent，使它们分别负责具体的操作任务。这种设计不仅让系统更加模块化，还能最大限度地发挥 Planner Agent 的协调作用。\n",
    "\n",
    "> 需要确保agent变量名与Planner Agent的instructions中定义的agent变量名一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cfc5b0724c85049f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:52:04.424560Z",
     "start_time": "2024-11-27T13:52:03.707504Z"
    }
   },
   "outputs": [],
   "source": [
    "# 员工信息查询agent\n",
    "employee_info_agent = Assistants.create(\n",
    "    model=\"qwen-plus\",\n",
    "    name='员工信息查询助手',\n",
    "    description='一个智能助手，能够查询员工信息。',\n",
    "    instructions='''你是员工信息查询助手，负责查询员工姓名、部门、HR等信息''',\n",
    "    tools=[\n",
    "        {\n",
    "            'type': 'function',\n",
    "            'function': {\n",
    "                'name': '查询员工信息',\n",
    "                'description': '当需要查询员工信息时非常有用，比如查询员工张三的HR是谁，查询教育部门总人数等。',\n",
    "                'parameters': {\n",
    "                    'type': 'object',\n",
    "                    'properties': {\n",
    "                        'query': {\n",
    "                            'type': 'str',\n",
    "                            'description': '用户的提问。'\n",
    "                        },\n",
    "                    },\n",
    "                    'required': ['query']},\n",
    "            }\n",
    "        }\n",
    "    ]\n",
    ")\n",
    "print(f'{employee_info_agent.name}创建完成')\n",
    "\n",
    "# 请假申请agent\n",
    "leave_agent = Assistants.create(\n",
    "    model=\"qwen-plus\",\n",
    "    name='请假申请助手',\n",
    "    description='一个智能助手，能够帮助员工提交请假申请。',\n",
    "    instructions='''你是员工请假申请助手，负责帮助员工提交请假申请。''',\n",
    "    tools=[\n",
    "        {\n",
    "            'type': 'function',\n",
    "            'function': {\n",
    "                'name': '发送请假申请',\n",
    "                'description': '当需要帮助员工发送请假申请时非常有用。',\n",
    "                'parameters': {\n",
    "                    'type': 'object',\n",
    "                    'properties': {\n",
    "                        # 需要请假的时间\n",
    "                        'date': {\n",
    "                            'type': 'str',\n",
    "                            'description': '员工想要请假的时间。'\n",
    "                        },\n",
    "                    },\n",
    "                    'required': ['date']},\n",
    "            }\n",
    "        }\n",
    "    ]\n",
    ")\n",
    "print(f'{leave_agent.name}创建完成')\n",
    "# 功能是回复日常问题。对于日常问题来说，可以使用价格较为低廉的模型作为agent的基座\n",
    "chat_agent = Assistants.create(\n",
    "    # 因为该Agent对大模型性能要求不高，因此使用成本较低的qwen-turbo模型\n",
    "    model=\"qwen-turbo\",\n",
    "    name='回答日常问题的机器人',\n",
    "    description='一个智能助手，解答用户的问题',\n",
    "    instructions='请礼貌地回答用户的问题'\n",
    ")\n",
    "print(f'{chat_agent.name}创建完成')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcc766094c1d25db",
   "metadata": {},
   "source": [
    "### 3.3创建Summary Agent并测试Multi-Agent效果\n",
    "在完成了Planner Agent和执行工具函数Agent的创建后，你还需要创建Summary Agent，该Agent会根据用户的问题与之前Agent输出的参考信息，全面、完整地回答用户问题。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27f870277050229f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:52:16.806910Z",
     "start_time": "2024-11-27T13:52:16.608809Z"
    }
   },
   "outputs": [],
   "source": [
    "summary_agent = Assistants.create(\n",
    "    model=\"qwen-plus\",\n",
    "    name='总结机器人',\n",
    "    description='一个智能助手，根据用户的问题与参考信息，全面、完整地回答用户问题',\n",
    "    instructions='你是一个智能助手，根据用户的问题与参考信息，全面、完整地回答用户问题'\n",
    ")\n",
    "print(f'{summary_agent.name}创建完成')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f5271311e4b14de",
   "metadata": {},
   "source": [
    "你可以将以上步骤封装为一个get_multi_agent_response函数，这样可以将复杂的多 Agent 协作过程抽象为一个简单接口。通过这种封装方式，用户只需提供输入问题，函数将：\n",
    "\n",
    "1. 调用 Planner Agent，规划任务顺序。\n",
    "2. 根据规划顺序依次调用对应的工具函数 Agent 执行任务。\n",
    "3. 汇总所有任务结果，最后通过 Summary Agent 生成最终回答。\n",
    "\n",
    "这种设计不仅让主流程更加清晰，还便于复用和扩展。\n",
    "\n",
    "> 由于列表中的元素为字符串，因此通过一个agent_mapper方法将字符串格式的Agent映射到定义好的Agent对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e8ffdb346784107",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-11-27T13:53:13.623003Z",
     "start_time": "2024-11-27T13:52:52.716124Z"
    }
   },
   "outputs": [],
   "source": [
    "# 将列表中的字符串映射到Agent对象上\n",
    "# 将字符串格式的Agent名称映射到具体Agent对象\n",
    "agent_mapper = {\n",
    "    \"employee_info_agent\": employee_info_agent,\n",
    "    \"leave_agent\": leave_agent,\n",
    "    \"chat_agent\": chat_agent\n",
    "}\n",
    "\n",
    "def get_multi_agent_response(query):\n",
    "    # 获取Agent的运行顺序\n",
    "    agent_order = get_agent_response(planner_agent,query)\n",
    "    # 由于大模型输出可能不稳定，因此加入异常处理模块处理列表字符串解析失败的问题\n",
    "    try:\n",
    "        order_stk = ast.literal_eval(agent_order)\n",
    "        print(\"Planner Agent正在工作：\")\n",
    "        for i in range(len(order_stk)):\n",
    "            print(f'第{i+1}步调用：{order_stk[i]}')\n",
    "        # 随着多Agent的加入，需要将Agent的输出添加到用户问题中，作为参考信息\n",
    "        cur_query = query\n",
    "\n",
    "        Agent_Message = \"\"\n",
    "        # 依次运行Agent\n",
    "        for i in range(len(order_stk)):\n",
    "            cur_agent = agent_mapper[order_stk[i]]\n",
    "            response = get_agent_response(cur_agent,cur_query)\n",
    "            Agent_Message += f\"*{order_stk[i]}*的回复为：{response}\\n\\n\"\n",
    "            # 如果当前Agent为最后一个Agent，则将其输出作为Multi Agent的输出\n",
    "            if i == len(order_stk)-1:\n",
    "                prompt = f\"请参考已知的信息：{Agent_Message}，回答用户的问题：{query}。\"\n",
    "                multi_agent_response = get_agent_response(summary_agent,prompt)\n",
    "                print(f'Multi-Agent回复为：{multi_agent_response}')\n",
    "                return multi_agent_response\n",
    "            # 如果当前Agent不是最后一个Agent，则将上一个Agent的输出response添加到下一轮的query中，作为参考信息\n",
    "            else:\n",
    "                # 在参考信息前后加上特殊标识符，可以防止大模型混淆参考信息与提问\n",
    "                cur_query = f\"你可以参考已知的信息：{response}你要完整地回答用户的问题。问题是：{query}。\"\n",
    "    # 兜底策略，如果上述程序运行失败，则直接调用大模型\n",
    "    except Exception as e:\n",
    "        return get_agent_response(chat_agent,query)\n",
    "\n",
    "# 此处来用 “王五在哪个部门？帮我提交下周三请假的申请”进行一个测试\n",
    "get_multi_agent_response(\"王五在哪个部门？帮我提交下周三请假的申请\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c94b161112b6627d",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "## 4.大模型平台的多智能体编排功能\n",
    "上一小节你了解了Multi-Agent系统的设计理念与实现方法。可以看出，在自主构建一个Multi-Agent系统时，虽然能够提供高度的灵活性，但也伴随着一定的工作量和复杂性。对于许多企业来说，快速实现复杂业务逻辑更为重要。\n",
    "\n",
    "[Dify](https://Dify.ai) 是智能体流程画布的开创者之一。Dify的画布工具让用户能够通过可视化的流程图来设计和管理智能体任务的执行逻辑，在业内树立了标杆。其直观的界面设计，为许多开发者提供了参考。\n",
    "\n",
    "尽管 Dify.ai 在智能体流程编排方面表现出色，但相比之下，百炼平台在以下方面更适合国内企业与开发者使用\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i4/O1CN01lbXhaX1WpOeXxkHwi_!!6000000002837-2-tps-1672-944.png\" alt=\"image\" width=\"700\">\n",
    "\n",
    "百炼平台的多智能体编排功能，依托大模型的强大能力，支持智能体自主决策和任务分工。通过平台内置的画布式编排工具，用户可以轻松实现以下目标：\n",
    "- 定义智能体的执行逻辑：用户能够直观地通过画布式工具配置各个智能体的执行规则和逻辑链路。\n",
    "- 编排多个智能体之间的协作：通过模块化设计，各个智能体能够高效配合，完成复杂任务。\n",
    "- 快速验证业务效果：内置的仿真和测试功能，帮助用户快速验证编排逻辑是否符合预期。\n",
    "\n",
    "百炼平台支持用户从零开始设计智能体系统，即使没有深厚的技术背景，用户也可以通过其易用的界面快速搭建并验证多智能体协作系统。对于需要快速落地多智能体技术的企业，百炼平台是一个理想的选择。\n",
    "\n",
    "详情请见[百炼智能体应用](https://help.aliyun.com/zh/model-studio/user-guide/single-agent-application)。\n",
    "\n",
    "## 5.本章小结\n",
    "在本节课程中，你学习了以下内容：\n",
    "- Agent系统的核心理念\n",
    "- 多智能体系统的构建原理和实现方法\n",
    "- 大模型平台的多智能体编排\n",
    "\n",
    "\n",
    "### 扩展阅读\n",
    "\n",
    "你已经了解了如何使用多智能体系统来处理复杂任务，但这种设计不仅仅局限于基础操作。事实上，合理设计多智能体系统，能够帮助你将更为复杂的业务流程自动化处理，提升效率和精度，甚至可以实现一些需要人介入的繁琐操作。\n",
    "\n",
    "##### 效果展示与流程图解析\n",
    "下面是一个实际的智能体系统应用案例，假设你需要对一个网页进行内容验证——不仅仅是检查静态内容，还包括与页面元素的交互。此时，多智能体系统的优势就显现出来了。通过分工合作，多个智能体能够共同高效完成任务，而你无需手动干预每个步骤。\n",
    "\n",
    "<img src=\"https://gw.alicdn.com/imgextra/i1/O1CN01w0oXM41YyfuOvSLsR_!!6000000003128-2-tps-1567-668.png\" alt=\"image\" width=\"700\">\n",
    "\n",
    "当面对一个需要解析HTML页面并执行特定操作的任务时，多智能体的分工如下：\n",
    "\n",
    "- Planner Agent规划器：分解任务，例如识别HTML元素中的列表或按钮。\n",
    "- Selector Agent执行器：负责具体的操作任务，例如选择特定元素并执行点击动作。\n",
    "- Monitor Agent监视器：实时监控任务的执行，确保流程按计划完成，如检测是否点击正确的按钮。\n",
    "\n",
    "##### 视觉标注在智能体应用中的结合\n",
    "在实际项目中，尤其是涉及到网页内容交互时，光靠传统的文本分析能力往往无法精确地识别界面元素，导致智能体的执行效率和准确性下降。为了解决这一问题，视觉标注被应用于自动化测试和控制台界面的元素识别。\n",
    "\n",
    "在某些场景下，智能体在操作控制台界面时，由于界面元素的复杂性，模型可能难以准确地识别目标。此时，通过引入视觉标注技术，可以提高模型对界面元素的理解和操作准确性。\n",
    "\n",
    "就像你在使用一款软件时，可能需要通过眼睛去识别按钮、下拉菜单或表单，才能做出正确的操作。如果没有清晰的视觉标识，你可能会找错位置或操作错误。类似地，在智能体执行任务时，界面元素的准确识别至关重要。通过视觉标注，系统可以为这些元素加上“标签”，帮助智能体更准确地“看到”并正确执行操作。\n",
    "\n",
    "例如，在处理HTML页面的自动化测试时，通过使用视觉标注，系统可以对页面中的按钮、链接或表单进行标记，并指导智能体精确选择和操作这些元素。这种视觉增强的设计大大提高了模型处理复杂界面的能力，使得多智能体系统在面对不同的UI元素时更加灵活高效。\n",
    "\n",
    "##### 多智能体系统的灵活性与技术结合\n",
    "通过这个案例，你可以发现多智能体系统的设计并不局限于某一类技术或场景。虽然在这个示例中多智能体系统结合了视觉标注，但实际上，多智能体系统的灵活性使得它能够与各种技术结合，解决不同的需求。\n",
    "\n",
    "例如，你可以结合机器学习技术优化智能体的决策过程，或者使用图像处理技术提升界面元素的识别能力。\n",
    "\n",
    "又如，你可以将多智能体系统与自然语言处理结合，使其能够理解和生成更复杂的语言指令，应用于智能客服或文本分析场景。\n",
    "\n",
    "你可以根据需求自由选择多智能体系统的架构和技术组合，最终为你带来最佳的自动化解决方案。\n",
    "\n",
    "多智能体系统不仅是一个工具，它更是一种思维方式，能够根据不同任务和场景灵活设计和调整，从而在不同的应用中发挥巨大价值。在今后的应用中，无论是面对页面自动化测试，还是处理更复杂的业务流程，合理设计的多智能体系统都能帮助你提高效率，优化操作，甚至替代部分人类工作，解放你的生产力。\n",
    "## 6.课后小测验\n",
    "【单选题】 在本教程中，Planner Agent的作用是什么？（ ）\n",
    "\n",
    "A. 负责执行工具函数\n",
    "\n",
    "B. 负责总结Agent的输出内容\n",
    "\n",
    "C. 负责分析用户输入，并将任务分发到其它Agent上\n",
    "\n",
    "D. 负责打印关键日志\n",
    "\n",
    "答案：C\n",
    "\n",
    "解析：\n",
    "\n",
    "在本教程中，Planner Agent 的主要功能是作为任务的分配者。它分析用户的输入内容，并根据具体任务需求，将不同的任务分配给最适合的其他 Agent。因此，Planner Agent在系统中起到路由的作用，确保每个任务能够高效地交由相应的 Agent 处理。\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "035de126",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "learnacp",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
